#include "postgres.c"

#include "utils.c"
#include "threadpool.c"

#define PG_CONN_INFO "sslmode=allow connect_timeout=3 host=localhost port=5432 dbname=zjdb"
#define DROP_TB "DROP TABLE IF EXISTS utests"
#define CREATE_TB "CREATE TABLE IF NOT EXISTS utests (id INT, age INT)"
#define INSERT_ROWS "INSERT INTO utests (id, age) VALUES (1, 20),(2, 21),(3, 19)"
#define INSERT_ROWS_PARAMS "INSERT INTO utests (id, age) VALUES ($1, $2),($1, $2),($1, $2)"
#define QUERY_ROWS "SELECT * FROM utests"

void
__thread_env_checker(void){
    SoN(0, postgres.thread_safe_checker());
}

void
__conn(void){
    Error *e = nil;
    PGconn *conn = nil;

    So(1, postgres.conn_checker(PG_CONN_INFO));

    __check_fatal(e, postgres.conn(PG_CONN_INFO, &conn));
    SoN(nil, conn);
    postgres.conn_drop(conn);
}

void
__do(void){
    Error *e = nil;
    PGconn *conn = nil;
    PGresult *reshdr = nil;
    struct QueryRes query_res;

    __check_fatal(e, postgres.conn(PG_CONN_INFO, &conn));

    __check_fatal(e, postgres.exec(conn, DROP_TB, &reshdr));
    postgres.res_drop(reshdr, nil);

    __check_fatal(e, postgres.exec(conn, CREATE_TB, &reshdr));
    postgres.res_drop(reshdr, nil);

    __check_fatal(e, postgres.exec(conn, INSERT_ROWS, &reshdr));
    postgres.res_drop(reshdr, nil);

    __check_fatal(e, postgres.query(conn, QUERY_ROWS, &reshdr));
    postgres.query_res_parse(reshdr, &query_res);
    So(2, query_res.fields_cnt);
    So(3, query_res.tuples_cnt);
    So(0, strcmp("id", query_res.fields[0]));
    So(0, strcmp("age", query_res.fields[1]));
    So(0, strcmp("1", query_res.tuples[0][0]));
    So(0, strcmp("20", query_res.tuples[0][1]));
    So(0, strcmp("2", query_res.tuples[1][0]));
    So(0, strcmp("21", query_res.tuples[1][1]));
    So(0, strcmp("3", query_res.tuples[2][0]));
    So(0, strcmp("19", query_res.tuples[2][1]));
    postgres.res_drop(reshdr, &query_res);

    postgres.conn_drop(conn);
}

void
__do_once(void){
    Error *e = nil;
    PGresult *reshdr = nil;
    struct QueryRes query_res;

    __check_fatal(e, postgres.exec_once(PG_CONN_INFO, DROP_TB));
    __check_fatal(e, postgres.exec_once(PG_CONN_INFO, CREATE_TB));
    __check_fatal(e, postgres.exec_once(PG_CONN_INFO, INSERT_ROWS));

    __check_fatal(e, postgres.query_once(PG_CONN_INFO, QUERY_ROWS, &reshdr, &query_res));
    postgres.query_res_parse(reshdr, &query_res);
    So(2, query_res.fields_cnt);
    So(3, query_res.tuples_cnt);
    So(0, strcmp("id", query_res.fields[0]));
    So(0, strcmp("age", query_res.fields[1]));
    So(0, strcmp("1", query_res.tuples[0][0]));
    So(0, strcmp("20", query_res.tuples[0][1]));
    So(0, strcmp("2", query_res.tuples[1][0]));
    So(0, strcmp("21", query_res.tuples[1][1]));
    So(0, strcmp("3", query_res.tuples[2][0]));
    So(0, strcmp("19", query_res.tuples[2][1]));
    postgres.res_drop(reshdr, &query_res);
}

void
__do_params(void){
    Error *e = nil;
    PGconn *conn = nil;
    PGresult *reshdr = nil;
    struct QueryRes query_res;

    __check_fatal(e, postgres.conn(PG_CONN_INFO, &conn));

    __check_fatal(e, postgres.exec_params(conn, DROP_TB, 0, nil, &reshdr));
    postgres.res_drop(reshdr, nil);

    __check_fatal(e, postgres.exec_params(conn, CREATE_TB, 0, nil, &reshdr));
    postgres.res_drop(reshdr, nil);

    const char *params[2] = {"1", "20"};
    __check_fatal(e, postgres.exec_params(conn, INSERT_ROWS_PARAMS, 2, params, &reshdr));
    postgres.res_drop(reshdr, nil);

    __check_fatal(e, postgres.query_params(conn, QUERY_ROWS, 0, nil, &reshdr));
    postgres.query_res_parse(reshdr, &query_res);
    So(2, query_res.fields_cnt);
    So(3, query_res.tuples_cnt);
    So(0, strcmp("id", query_res.fields[0]));
    So(0, strcmp("age", query_res.fields[1]));
    So(0, strcmp("1", query_res.tuples[0][0]));
    So(0, strcmp("20", query_res.tuples[0][1]));
    So(0, strcmp("1", query_res.tuples[1][0]));
    So(0, strcmp("20", query_res.tuples[1][1]));
    So(0, strcmp("1", query_res.tuples[2][0]));
    So(0, strcmp("20", query_res.tuples[2][1]));
    postgres.res_drop(reshdr, &query_res);

    postgres.conn_drop(conn);
}

void
__do_prepared(void){
    Error *e = nil;
    PGconn *conn = nil;
    PGresult *prepare_hdr = nil;
    PGresult *reshdr = nil;
    struct QueryRes query_res;

    __check_fatal(e, postgres.conn(PG_CONN_INFO, &conn));

    __check_fatal(e, postgres.exec(conn, DROP_TB, &reshdr));
    postgres.res_drop(reshdr, nil);

    __check_fatal(e, postgres.exec(conn, CREATE_TB, &reshdr));
    postgres.res_drop(reshdr, nil);

    const char *params[2] = {"1", "20"};
    __check_fatal(e, postgres.prepare(conn, INSERT_ROWS_PARAMS, "p1", 2, &prepare_hdr));

    __check_fatal(e, postgres.exec_prepared(conn, "p1", 2, params, &reshdr));
    postgres.res_drop(reshdr, nil);
    __check_fatal(e, postgres.exec_prepared(conn, "p1", 2, params, &reshdr));
    postgres.res_drop(reshdr, nil);
    __check_fatal(e, postgres.exec_prepared(conn, "p1", 2, params, &reshdr));
    postgres.res_drop(reshdr, nil);

    postgres.res_drop(prepare_hdr, nil);

    __check_fatal(e, postgres.query(conn, QUERY_ROWS, &reshdr));
    postgres.query_res_parse(reshdr, &query_res);
    So(2, query_res.fields_cnt);
    So(9, query_res.tuples_cnt);
    So(0, strcmp("id", query_res.fields[0]));
    So(0, strcmp("age", query_res.fields[1]));
    postgres.res_drop(reshdr, &query_res);

    postgres.conn_drop(conn);
}

void
__pgpool(void){
    postgres.pool_init(PG_CONN_INFO);

    postgres.pool_addjob(DROP_TB);
    postgres.pool_addjob(CREATE_TB);

    postgres.pool_addjob(INSERT_ROWS);
    postgres.pool_addjob(INSERT_ROWS);
    postgres.pool_addjob(INSERT_ROWS);
    postgres.pool_addjob(INSERT_ROWS);
    postgres.pool_addjob(INSERT_ROWS);

    Error *e = nil;
    PGresult *reshdr = nil;
    struct QueryRes query_res;

    __check_fatal(e, postgres.query_once(PG_CONN_INFO, QUERY_ROWS, &reshdr, &query_res));
    postgres.query_res_parse(reshdr, &query_res);
    So(2, query_res.fields_cnt);
    So(15, query_res.tuples_cnt);
    So(0, strcmp("id", query_res.fields[0]));
    So(0, strcmp("age", query_res.fields[1]));
    postgres.res_drop(reshdr, &query_res);
}

_i
main(void){
    __thread_env_checker();
    __conn();
    __do();
    __do_once();
    __do_params();
    __do_prepared();
    __pgpool();
}
